package com.nw.dsl4j.runtime.parser;

import java.io.InputStream;
import java.io.Reader;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

import org.antlr.runtime.ANTLRInputStream;
import org.antlr.runtime.ANTLRReaderStream;
import org.antlr.runtime.ANTLRStringStream;
import org.antlr.runtime.CharStream;
import org.antlr.runtime.CommonToken;
import org.antlr.runtime.CommonTokenStream;
import org.antlr.runtime.Lexer;
import org.antlr.runtime.Parser;
import org.antlr.runtime.ParserRuleReturnScope;
import org.antlr.runtime.TokenStream;
import org.apache.commons.lang.StringUtils;

import com.nw.dsl4j.runtime.adapter.Adaptable;
import com.nw.dsl4j.runtime.converters.ConverterManager;

public class AbstractReflectionDsl4jParserImpl<P> implements Dsl4jParser<P> {

	private Class<?> dslClass;
	
	private Class<?> classToParse;

	protected Class<? extends Lexer> lexerClazz;

	protected Class<? extends Parser> parserClazz;

	protected Method mainMethod;

	protected Dsl4jParserContext parserContext;

	
	public AbstractReflectionDsl4jParserImpl(Class<?> dslClass, Class<?> classToParse, Dsl4jParserContext parserContext) {
		this.parserContext = parserContext;
		this.setDslClass(dslClass);
		this.setClassToParse(classToParse);
	}

	protected String getParserClassName() {
		String className = getDslClass().getSimpleName();
		String prefix = getDslClass().getPackage().getName() + ".parser." + className;
		return prefix + "Parser";
	}

	protected String getLexerClassName() {
		String className = getDslClass().getSimpleName();
		String prefix = getDslClass().getPackage().getName() + ".parser." + className;
		return prefix + "Lexer";
	}
	
	public P parse(InputStream inputStream) throws Exception {
		CharStream input = new ANTLRInputStream(inputStream);
		return parse(input);
	}

	public P parse(Reader reader) throws Exception {
		CharStream input = new ANTLRReaderStream(reader);
		return parse(input);
	}
	public P parse(String string) throws Exception {
		CharStream input = new ANTLRStringStream(string);
		return parse(input);
	}
	@SuppressWarnings("unchecked")
	private P parse(CharStream input) throws InstantiationException, IllegalAccessException, NoSuchMethodException,
			InvocationTargetException, NoSuchFieldException {
		Lexer l = lexerClazz.newInstance();
		l.setCharStream(input);
		TokenStream tokenStream = new CommonTokenStream(l);
		Constructor<? extends Parser> ctor = parserClazz.getDeclaredConstructor(new Class[]{TokenStream.class});
		Parser parser = ctor.newInstance(new Object[]{tokenStream});
		parser.setTokenStream(tokenStream);
		
		Dsl4jParserContext pc = getParserContext();
		((AntlrParserContext)pc).setInput(input);
		parser.getClass().getDeclaredField("context").set(parser, pc);

		ParserRuleReturnScope rrs = (ParserRuleReturnScope) getParsingMethod().invoke(parser, new Object[0]);
		Field f = rrs.getClass().getDeclaredField("ret");
		Object ret = f.get(rrs);
		if (ret instanceof Adaptable) {
			com.nw.dsl4j.runtime.parser.LocationSupport locationSupport = ((com.nw.dsl4j.runtime.adapter.Adaptable) ret).getAdapter(com.nw.dsl4j.runtime.parser.LocationSupport.class);
			locationSupport.setStartIndex(((CommonToken) rrs.start).getStartIndex());
			locationSupport.setEndIndex(((CommonToken) rrs.stop).getStopIndex());
			locationSupport.setText(input.substring(locationSupport.getStartIndex(),locationSupport.getEndIndex()));
		}
		return (P) ret;
	}
	

	public void setParserContext(Dsl4jParserContext parserContext) {
		this.parserContext = parserContext;
	}
	public Dsl4jParserContext getParserContext() {
		if (parserContext == null) {
			AntlrParserContext antlrParserContext = new AntlrParserContext();
			antlrParserContext.setConverterManager(new ConverterManager());
			parserContext = antlrParserContext;
		}
		return parserContext;
	}

	protected Method getParsingMethod() throws NoSuchMethodException {
		if (mainMethod == null){
			mainMethod = parserClazz.getMethod(getParsingMethodName(), new Class[0]);
		}
		return mainMethod;
	}

	private String getParsingMethodName() {
		return StringUtils.uncapitalize(getClassToParse().getCanonicalName().replace(".", "_"));
	}

	protected void setDslClass(Class<?> dslClass) {
		this.dslClass = dslClass;
	}

	protected Class<?> getDslClass() {
		return dslClass;
	}

	protected void setClassToParse(Class<?> classToParse) {
		this.classToParse = classToParse;
	}

	protected Class<?> getClassToParse() {
		return classToParse;
	}
}